/*
 * graphics.c : wrapper to system-specific graphics engine
 *
 * Written by T.Pierron, June 2008.
 */

#include <stdlib.h>
#include <wchar.h>
#include "SIT_P.h"
#include "graphics_P.h"

static LPLOGFONT sitfont; /* Command-line provided system font */
APTR sithfont;

DLLIMP void GFX_InitGC(GC gc, HWND hwnd, HDC hdc, LPRECT ps, HFONT font, HBRUSH bg)
{
	memset(gc, 0, sizeof *gc);
	gc->hwnd = hwnd;
	gc->device = hdc;
	gc->refresh.x = ps->left;
	gc->refresh.y = ps->top;
	gc->refresh.width  = ps->right  - ps->left;
	gc->refresh.height = ps->bottom - ps->top;
	gc->bgcolor = -1;
	if (font)
	{
		gc->font = SelectObject(hdc, font);
		SetTextAlign(hdc, TA_TOP | TA_UPDATECP);
	}
	if (bg)
	{
		gc->brextern = True;
		gc->brush = SelectObject(gc->device, gc->fill = bg);
	}
}

DLLIMP GC GFX_GetGC(SIT_Widget w)
{
	if (w == NULL || w->sw_Handle == NULL) return NULL;

	/* We keep a cache of GC allocated so that GFX_GetGC() can be called at any
	 * time without wasting too much memory and not to worry about deallocation.
	 */
	if (w->sw_GC) { ((GC)w->sw_GC)->inuse ++; return w->sw_GC; }

	GC gc = calloc(sizeof *gc, 1);

	gc->ctrl = w;
	gc->free = 1;
	gc->inuse = 1;
	gc->device = GetDC(gc->hwnd = w->sw_Handle);
	gc->refresh.width  = w->sw_Box.width;
	gc->refresh.height = w->sw_Box.height;

	/* Default values */
	gc->font = SelectObject(gc->device, w->sw_Font);
	SetTextAlign(gc->device, TA_TOP | TA_UPDATECP);

	/* Keep them in a cache to avoid allocating unnecessary GC */
	return w->sw_GC = gc;
}

/* Offscreen compatible bitmap usually for backing store */
DLLIMP GC GFX_AllocOffScreen(SIT_Widget sw, int w, int h)
{
	HDC     hex = sw ? GetDC(sw->sw_Handle) : CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC     hdc = CreateCompatibleDC(hex);
	HBITMAP bmp = CreateCompatibleBitmap(hex, w, h);

	if (sw) ReleaseDC(sw->sw_Handle, hex);
	else    DeleteDC(hex);

	if (bmp == NULL) { DeleteDC(hdc); return NULL; }

	GC gc = calloc(sizeof *gc, 1);

	gc->free = 2;
	gc->bitmap = (HBITMAP) SelectObject(hdc, bmp);
	gc->device = hdc;
	gc->refresh.width  = w;
	gc->refresh.height = h;

	/* Default values */
	gc->font = SelectObject(gc->device, GetStockObject(DEFAULT_GUI_FONT));
	SetTextAlign(gc->device, TA_TOP | TA_UPDATECP);

	return gc;
}

/* Set text overwriting mode */
DLLIMP void GFX_SetDrMd(GC gc, int mode)
{
	SetBkMode(gc->device, mode == GFX_MODEJAM1 ? TRANSPARENT :
	                      mode == GFX_MODEJAM2 ? OPAQUE : mode);
}

DLLIMP void GFX_SetPen(GC gc, int width, ULONG color)
{
	if (gc->pen)
		DeleteObject(SelectObject(gc->device, gc->pen));

	if (ISSYS(color))
		color = GetSysColor(sysColorsWin32[color & 0xffff]);

	gc->pen = width < 0 ? SelectObject(gc->device, GetStockObject(NULL_PEN)) :
		SelectObject(gc->device, CreatePen(PS_SOLID, width, color));
}

/* Move graphic cursor */
DLLIMP void GFX_Move(GC gc, int x, int y)
{
	MoveToEx(gc->device, x, y, NULL);
}

/* Draw a line from current point to given location */
DLLIMP void GFX_Draw(GC gc, int x, int y)
{
	LineTo(gc->device, x, y);
}

DLLIMP void GFX_DrawRect(GC gc, int x, int y, int x2, int y2)
{
	POINT pts[] = {{.x = x,  .y = y},  {.x = x2, .y = y},
	               {.x = x2, .y = y2}, {.x = x,  .y = y2}};
	BYTE  flg[] = {PT_MOVETO, PT_LINETO, PT_LINETO, PT_LINETO | PT_CLOSEFIGURE};

	PolyDraw(gc->device, pts, flg, 4);
}

DLLIMP void GFX_3dRect(GC gc, int x, int y, int w, int h, int borders, int raised)
{
	RECT r = {.left = x, .top = y, .right = x + w - 1, .bottom = y + h - 1};
	int  b = 0;
	int  f = raised ? BDR_RAISEDOUTER : BDR_SUNKENOUTER;
	if (borders & 1)  b |= BF_TOP;
	if (borders & 2)  b |= BF_RIGHT;
	if (borders & 4)  b |= BF_BOTTOM;
	if (borders & 8)  b |= BF_LEFT;
	if (borders & 16) f |= BDR_SUNKENINNER;
	if (borders & 32) f |= BDR_RAISEDINNER;
	DrawEdge(gc->device, &r, f, b);
}


DLLIMP void GFX_FillRect(GC gc, int x, int y, int x2, int y2)
{
	RECT r = {x, y, x2 + 1, y2 + 1};

	if (gc->fill == NULL)
		gc->fill = CreateSolidBrush(gc->bgcolor);

	FillRect(gc->device, &r, gc->fill);
}

DLLIMP void GFX_FillRectS(GC gc, Rect * r)
{
	RECT rect = {r->x, r->y, r->x + r->width, r->height + r->y};

	if (gc->fill == NULL)
		gc->fill = CreateSolidBrush(gc->bgcolor);

	FillRect(gc->device, &rect, gc->fill);
}

DLLIMP void GFX_SetPixel(GC gc, int x, int y)
{
	SetPixel(gc->device, x, y, gc->fgcolor);
}

DLLIMP void GFX_XorRect(GC gc, int x, int y, int w, int h)
{
	PatBlt(gc->device, x, y, w, h, PATINVERT);
}

DLLIMP void GFX_CopyGC(GC src, int xs, int ys, int ws, int hs, GC dst, int xd, int yd)
{
	BitBlt(dst->device, xd, yd, ws, hs, src->device, xs, ys, SRCCOPY);
}

DLLIMP void GFX_ToNativeFormat(Image i)
{
	DATA8 p, s;
	int x, y;
	switch (i->format) {
	case GFX_FormatRGB:
		for (y = i->height, p = i->bitmap; y > 0; y ++, p += i->stride)
			for (x = i->width, s = p; x > 0; swap(s[0], s[2]), x --, s += 3);
		i->format = GFX_FormatBGR;
		break;
	case GFX_FormatRGBA:
		for (x = i->height * i->stride, p = i->bitmap; x > 0; x -= 4, swap(p[0], p[2]), p += 4);
		i->format = GFX_FormatABGR;
		// no break;
	case GFX_FormatABGR:
		if (i->encoder & GFX_Premultiplied) break;
		for (x = i->height * i->stride, p = i->bitmap; x > 0; x -= 4, p += 4) {
			p[0] = p[0] * p[3] / 255;
			p[1] = p[1] * p[3] / 255;
			p[2] = p[2] * p[3] / 255;
		}
		i->encoder |= GFX_Premultiplied;
	}
}

DLLIMP void GFX_SetPixels(Image src, int xs, int ys, int ws, int hs,
                          GC dst, int xd, int yd, int wd, int hd)
{
	if (wd == 0 && hd == 0) wd = ws, hd = hs; else
	if (hd == 0) wd = hd * ws / hs; else
	if (wd == 0) hd = wd * hs / ws;

	if (src->hbitmap && src->format != GFX_FormatK)
	{
		HDC     hdc = CreateCompatibleDC(dst->device);
		HBITMAP old = SelectObject(hdc, src->hbitmap);

		SetStretchBltMode(hdc, COLORONCOLOR);
		if (src->format >= GFX_FormatRGBA)
		{
			BLENDFUNCTION bf = {
				.BlendOp = AC_SRC_OVER,
				.AlphaFormat = AC_SRC_ALPHA,
				.SourceConstantAlpha = 255
			};

			pAlphaBlend(dst->device, xd, yd, wd, hd, hdc, xs, ys, ws, hs, bf);
		}
		else if (ws == wd && hs == hd)
			BitBlt(dst->device, xd, yd, wd, hd, hdc, xs, ys, SRCCOPY);
		else
			StretchBlt(dst->device, xd, yd, wd, hd, hdc, xs, ys, ws, hs, SRCCOPY);
		SelectObject(hdc, old);
		DeleteDC(hdc);
	}
	else
	{
		LPBITMAPINFO info = alloca(sizeof *info + (src->format == GFX_FormatK ?
			sizeof info->bmiColors * 255 : 0));
		if (ys >= src->height) return;

		memset(info, 0, sizeof *info);
		info->bmiHeader.biSize = sizeof info->bmiHeader;
		info->bmiHeader.biWidth = src->width;
		info->bmiHeader.biHeight = - (src->height-ys); /* Top-down bitmap, not bottom-up */
		info->bmiHeader.biPlanes = 1;
		info->bmiHeader.biBitCount = src->bpp;
		if (src->format == GFX_FormatK)
		{
			RGBQUAD * p;
			int       i;
			for (i = 1, p = info->bmiColors + 1; i < 256; i ++, p ++)
				p->rgbBlue = p->rgbRed = p->rgbGreen = i, p->rgbReserved = 0;
		}
		SetStretchBltMode(dst->device, COLORONCOLOR);
		if (ws == wd && hs == hd)
		{
			/* LOL WUT: vertical offset does not seem to work. Horizontal do. Yay! */
			SetDIBitsToDevice(dst->device, xd, yd, ws, hs, xs, 0, 0, hs, src->bitmap + ys * src->stride,
				info, DIB_RGB_COLORS);
		}
		else if (ys > 0)
		{
			// Re-LOL Re-WUT: ys relative to bottom edge
			info->bmiHeader.biHeight = (src->height-ys);
			StretchDIBits(dst->device, xd, yd+hd, wd, -hd, xs, 0, ws, hs, src->bitmap+ys*src->stride,
				info, DIB_RGB_COLORS, SRCCOPY);
		}
		else
		{
			StretchDIBits(dst->device, xd, yd, wd, hd, xs, 0, ws, hs, src->bitmap,
				info, DIB_RGB_COLORS, SRCCOPY);
		}
	}
}

DLLIMP void GFX_Scroll(SIT_Widget ctrl, int dx, int dy, int x, int y, int w, int h)
{
	RECT r = {.left = x, .top = y, .right = x+w-1, .bottom = y+h-1};

	ScrollWindowEx(ctrl->sw_Handle, dx, dy, r.right < 0 ? NULL : &r,
		NULL, NULL, NULL, SW_ERASE	| SW_INVALIDATE);
}

DLLIMP void GFX_FlattenImage(Image img, ULONG color)
{
	if (img->format < GFX_FormatRGBA) return;
	if (ISSYS(color))
		color = GetSysColor(sysColorsWin32[color & 0xffff]);

	uint8_t rgb[3];
	DATA8   p;
	int     i, j;

	rgb[2] = color & 0xff; color >>= 8;
	rgb[1] = color & 0xff; color >>= 8;
	rgb[0] = color & 0xff; color >>= 8;

	for (j = 0, p = img->bitmap; j < img->height; j ++)
	{
		if (img->encoder & GFX_Premultiplied)
		{
			for (i = 0; i < img->width; i++, p += 4)
			{
				/* Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red  */
				p[0] += (255 - p[3]) * rgb[0] / 255;
				p[1] += (255 - p[3]) * rgb[1] / 255;
				p[2] += (255 - p[3]) * rgb[2] / 255;
				p[3]  = 255;
			}
		} else {
			for (i = 0; i < img->width; i++, p += 4)
			{
				/* Dst.Red = Src.Red * Src.alpha + (1 - Src.Alpha) * Dst.Red  */
				p[0] = p[0] * p[3] / 255 + (255 - p[3]) * rgb[0] / 255;
				p[1] = p[1] * p[3] / 255 + (255 - p[3]) * rgb[1] / 255;
				p[2] = p[2] * p[3] / 255 + (255 - p[3]) * rgb[2] / 255;
				p[3]  = 255;
			}
		}
	}
}

/* Used with text output */
DLLIMP void GFX_SetTextColor(GC gc, int color)
{
	SetTextColor(gc->device, gc->fgcolor = color);
}

#define	GFX_DELBGBRUSH(gc) { \
	if (gc->brush) SelectObject(gc->device, gc->brush); \
	if (!gc->brextern) DeleteObject(gc->fill); \
	gc->fill = gc->brush = NULL; gc->brextern = 0; \
}

/* Used with text output when DrMd == JAM2 */
DLLIMP void GFX_SetBgColor(GC gc, int color)
{
	SetBkColor(gc->device, color);
	if ((gc->bgcolor != color && gc->fill) || gc->brush)
		GFX_DELBGBRUSH(gc);
	gc->bgcolor = color;
}

DLLIMP void GFX_SetBgFill(GC gc, APTR bitmap)
{
	if (gc->fill) GFX_DELBGBRUSH(gc);
	gc->bgcolor = (ULONG) -1;
	gc->brextern = True;
	gc->fill = bitmap;
	gc->brush = SelectObject(gc->device, gc->fill);
}

DLLIMP void GFX_SwapFgBg(GC gc)
{
	if (gc->bgcolor != (ULONG) -1)
	{
		if (gc->pen)
			DeleteObject(SelectObject(gc->device, gc->pen));
		if (gc->fill) GFX_DELBGBRUSH(gc);
		ULONG tmp = gc->fgcolor;
		SetTextColor(gc->device, gc->fgcolor = gc->bgcolor);
		SetBkColor(gc->device, gc->bgcolor = tmp);
	}
}

int sysColorsWin32[] = {
	COLOR_WINDOWTEXT,      // Text in window
	COLOR_3DDKSHADOW,      // Dark shadow for 3D elements
	COLOR_3DFACE,          // Face color for 3D elements and for dialog box bg
	COLOR_3DHIGHLIGHT,     // Highlight color for 3D elements (for edges facing the light source)
	COLOR_3DLIGHT,         // Light color for 3D elements (for edges facing the light source.)
	COLOR_3DSHADOW,        // Shadow color for 3D elements (for edges away from ").
	COLOR_BACKGROUND,      // Desktop.
	COLOR_BTNFACE,         // Face color for 3D elements and for dialog box backgrounds.
	COLOR_BTNTEXT,         // Text on push buttons.
	COLOR_CAPTIONTEXT,     // Text in caption, size box, and scroll bar arrow box.
	COLOR_GRAYTEXT,        // Grayed (disabled) text.
	COLOR_HIGHLIGHT,       // Item(s) selected in a control.
	COLOR_HIGHLIGHTTEXT,   // Text of item(s) selected in a control.
	COLOR_HOTLIGHT,        // Color for a hyperlink or hot-tracked item.
	COLOR_INFOBK,          // Background color for tooltip controls.
	COLOR_INFOTEXT,        // Text color for tooltip controls.
	COLOR_MENU,            // Menu background.
	COLOR_MENUTEXT,        // Text in menus.
	COLOR_MENUBAR,         // Menu selected background
	COLOR_MENUHILIGHT,     // Menu selected text
	COLOR_WINDOW           // Window background.
};

DLLIMP int GFX_SetSysColor(GC gc, int color, Bool bg)
{
	DWORD rgb = GetSysColor(sysColorsWin32[color]);

	if (gc)
	{
		if (bg)
		{
			if (rgb != gc->bgcolor)
			{
				SetBkColor(gc->device, gc->bgcolor = rgb);
				if (gc->fill) GFX_DELBGBRUSH(gc);
			}
		}
		else SetTextColor(gc->device, gc->fgcolor = rgb);
	}
	return rgb;
}

/* Measure length of an UTF8 text */
DLLIMP int GFX_TextLength(GC gc, STRPTR str, int nb)
{
	SIZE   sz;
	int    len = MultiByteToWideChar(CP_UTF8, 0, str, nb, NULL, 0);
	LPWSTR dup = alloca(len * sizeof *dup);

	len = MultiByteToWideChar(CP_UTF8, 0, str, nb, dup, len);
	if (nb < 0) len --; // MBToWC will include final 0 in return value

	GetTextExtentPoint32(gc->device, dup, len, &sz);

	return sz.cx;
}

DLLIMP int GFX_TextLengthW(GC gc, const wchar_t * str, int len)
{
	SIZE sz;
	GetTextExtentPoint32(gc->device, str, len, &sz);
	return sz.cx;
}

/* How many bytes fit in the specified width */
DLLIMP int GFX_TextFit(GC gc, STRPTR str, int nb, int maxwidth, int * width)
{
	INT    fit = 0;
	SIZE   size;
	int    len = MultiByteToWideChar(CP_UTF8, 0, str, nb, NULL, 0);
	LPWSTR dup = alloca(len * sizeof *dup);
	LPINT  dx  = alloca(len * sizeof *dx);

	len = MultiByteToWideChar(CP_UTF8, 0, str, nb, dup, len);

	GetTextExtentExPoint(gc->device, dup, len, maxwidth, &fit, dx, &size);

	if (width) *width = fit > 0 ? dx[fit - 1] : 0;

	return NthChar(str, fit) - str;
}

/* Draw UTF8 text */
DLLIMP int GFX_Text(GC gc, STRPTR str, int nb, int x, int y)
{
	POINT  pos;
	RECT   rect;
	int    len = MultiByteToWideChar(CP_UTF8, 0, str, nb, NULL, 0);
	LPWSTR dup = alloca(len * sizeof *dup);

	len = MultiByteToWideChar(CP_UTF8, 0, str, nb, dup, len);
	if (nb < 0) len --;

	rect.left = x;
	rect.top = y;
	rect.right = 32000;
	rect.bottom = y + 32000;

	/* Hmmm, don't know why GDI starts one pixel left from specified position */
	MoveToEx(gc->device, x, y, NULL);
	//TextOut(gc->device, 0, 0, dup, len);
	ExtTextOut(gc->device, x, y, ETO_CLIPPED, &rect, dup, len, NULL);

	GetCurrentPositionEx(gc->device, &pos);

	return pos.x;
}

DLLIMP int GFX_TextW(GC gc, const wchar_t * text, int len, int x, int y)
{
	POINT pos;
	RECT  rect;

	rect.left = x;
	rect.top = y;
	rect.right = 32000;
	rect.bottom = y + 32000;

	if (len < 0) len = wcslen(text);

	MoveToEx(gc->device, x, y, NULL);
	ExtTextOut(gc->device, x, y, ETO_CLIPPED, &rect, text, len, NULL);

	GetCurrentPositionEx(gc->device, &pos);

	return pos.x;
}


/* Fill a polygon area */
DLLIMP void GFX_FillPolygon(GC gc, int * points, int nb, int mode)
{
	POINT * p = alloca(sizeof *p * nb);
	POINT * r;
	int     i;

	p->x = points[0];
	p->y = points[1]; points += 2;

	if ((mode & 1) == GFX_CoordModePrevious)
	{
		for (i = 1, r = p + 1; i < nb; i ++, r ++)
		{
			r->x = r[-1].x + *points++;
			r->y = r[-1].y + *points++;
		}
	}
	else /* GFX_CoordModeAbs */
	{
		for (i = 0, r = p + 1; i < nb; i ++, r->x = *points++, r->y = *points++, r++);
	}

	if (gc->fill == NULL)
		gc->fill = CreateSolidBrush(gc->bgcolor);

	if (gc->brush == NULL)
		/* Created, but not selected */
		gc->brush = SelectObject(gc->device, gc->fill);

	if (gc->pen == NULL && gc->fgcolor > 0)
		gc->pen = SelectObject(gc->device, CreatePen(PS_SOLID, 1, gc->fgcolor));

	SetPolyFillMode(gc->device, WINDING);
	Polygon(gc->device, p, nb);
}

/* Draw a polygon using lines or bezier curves */
DLLIMP void GFX_Polygon(GC gc, int * points, int nb, int mode)
{
	POINT * p = alloca(sizeof *p * nb);
	BYTE  * m = alloca(nb);
	POINT * r;
	int     i, flag;

	p->x = points[0];
	p->y = points[1]; points += 2;
	m[0] = PT_MOVETO;
	flag = mode & GFX_BezierCurve ? PT_BEZIERTO : PT_LINETO;

	if ((mode & 1) == GFX_CoordModePrevious)
	{
		for (i = 1, r = p + 1; i < nb; i ++, r ++)
		{
			r->x = r[-1].x + *points++;
			r->y = r[-1].y + *points++;
			m[i] = flag;
		}
	}
	else /* GFX_CoordModeAbs */
	{
		for (i = 1, r = p + 1; i < nb; m[i] = flag, i ++, r->x = *points++, r->y = *points++, r++);
	}
	if (mode & GFX_ClosePolygon)
		m[i-1] |= PT_CLOSEFIGURE;

	if (gc->pen == NULL && gc->fgcolor > 0)
		gc->pen = SelectObject(gc->device, CreatePen(PS_SOLID, 1, gc->fgcolor));

	PolyDraw(gc->device, p, m, nb);
}

DLLIMP void GFX_FillElipse(GC gc, int x, int y, int x2, int y2)
{
	if (x2 < x) swap(x, x2);
	if (y2 < y) swap(y, y2);

	if (gc->pen == NULL && gc->fgcolor > 0)
		gc->pen = SelectObject(gc->device, CreatePen(PS_SOLID, 1, gc->fgcolor));

	if (gc->fill == NULL)
	{
		HGDIOBJ ret;
		if (gc->bgcolor != GFX_ColorTransparent)
		{
			if (gc->fill) SelectObject(gc->device, gc->brush), DeleteObject(gc->fill), gc->brush = 0;
			ret = SelectObject(gc->device, gc->fill = CreateSolidBrush(gc->bgcolor));
		}
		else ret = SelectObject(gc->device, GetStockBrush(NULL_BRUSH));
		if (! gc->brush) gc->brush = ret;
	}
	Ellipse(gc->device, x, y, x2, y2);
}

DLLIMP void GFX_FillGradient(GC gc, Rect * r, int orient, ULONG col1, ULONG col2, ...)
{
	PTRIVERTEX vertex;
	ULONG *    mesh;
	va_list    args;
	int        step, i, mode, w, h;

	va_start(args, col2);
	for (step = 2; va_arg(args, ULONG) != GFX_ColorStop; step += 2);
	va_end(args);

	vertex = alloca(sizeof *vertex * step);
	mesh   = alloca(sizeof *mesh   * step);
	orient = ((orient + 45) / 90) & 3; // Only 0, 90, 180 or 270
	mode   = orient == 1 || orient == 3;
	w      = r->width - 1;
	h      = r->height - 1;

	va_start(args, col2);
	for (i = 0; i < step; )
	{
		// RGB(r, g, b)	((r) | ((g) << 8) | ((b) << 16))
		PTRIVERTEX v = vertex + i;
		mesh[i]   = i;
		mesh[i+1] = i + 1;
		v->x = i > 0 && mode == 0 ? v[-1].x : (orient == 2 ? r->x + w : r->x);
		v->y = i > 0 && mode == 1 ? v[-1].y : (orient == 1 ? r->y + h : r->y);
		v->Red   = (col1 & 0xff) << 8;
		v->Green = (col1 & 0xff00);
		v->Blue  = (col1 >> 8) & 0xff00;
		v->Alpha = 0; v ++; i += 2;
		v->x = (mode ? w : w * i / step); if (orient == 2) v->x = w - v->x; v->x += r->x;
		v->y = (mode ? h * i / step : h); if (orient == 1) v->y = h - v->y; v->y += r->y;
		v->Red   = (col2 & 0xff) << 8;
		v->Green = (col2 & 0xff00);
		v->Blue  = (col2 >> 8) & 0xff00;
		v->Alpha = 0;

		col1 = col2;
		col2 = va_arg(args, ULONG);
	}
	va_end(args);

	pGradientFill(gc->device, vertex, step, mesh, step >> 1, mode ? GRADIENT_FILL_RECT_V :
		GRADIENT_FILL_RECT_H);
}

/* GDI API is a PITA */
DLLIMP Bool GFX_SetPatBrush(GC gc, Image img)
{
	HDC          hex  = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC          hdc  = CreateCompatibleDC(hex);
	HBITMAP      bmp  = CreateCompatibleBitmap(hex, img->width, img->height);
	LPBITMAPINFO info = alloca(sizeof *info + (img->format == GFX_FormatK ?
		sizeof info->bmiColors * 255 : 0));

	DeleteDC(hex);
	if (bmp == 0) { DeleteDC(hdc); return False; }
	SelectObject(hdc, bmp);

	GFX_ToNativeFormat(img);
	memset(info, 0, sizeof *info);
	info->bmiHeader.biSize = sizeof info->bmiHeader;
	info->bmiHeader.biWidth = img->width;
	info->bmiHeader.biHeight = -img->height; /* Top-down bitmap, not bottom-up */
	info->bmiHeader.biPlanes = 1;
	info->bmiHeader.biBitCount = img->bpp;
	info->bmiHeader.biCompression = BI_RGB;

	if (img->format == GFX_FormatK)
	{
		RGBQUAD * p;
		int       i;
		for (i = 1, p = info->bmiColors + 1; i < 256; i ++, p ++)
			p->rgbBlue = p->rgbRed = p->rgbGreen = i, p->rgbReserved = 0;
	}

	SetDIBitsToDevice(hdc, 0, 0, img->width, img->height, 0, 0, 0, img->height,
		img->bitmap, info, DIB_RGB_COLORS);

	DeleteDC(hdc);
	GFX_DELBGBRUSH(gc);
	gc->fill = CreatePatternBrush(bmp);
	if (gc->fill == 0) return False;
	gc->brush = SelectObject(gc->device, gc->fill);

	return True;
}

DLLIMP APTR GFX_LoadImage(STRPTR img)
{
	int mode = 0;
	if (strncmp(img, "raw://", 6) == 0)
		img += 6, mode = 2;
	if (strncmp(img, "dib://", 6) == 0)
		img += 6, mode |= 4;
	if (strncmp(img, "data://", 7) == 0)
	{
		DATA8 ptr = (DATA8) img;
		int   sz  = (((((ptr[7] << 8) | ptr[8]) << 8) | ptr[9]) << 8) | ptr[10];

		return IMG_ReadFromBuf(ptr + 11, sz, mode);
	}
	else if (strncmp(img, "res://", 6) == 0)
	{
		return IMG_ReadFromRes(img + 6);
	}
	return IMG_Read(img, mode);
}

DLLIMP APTR GFX_LoadImageFromBuf(APTR data, int length)
{
	return IMG_ReadFromBuf(data, length, 0);
}

static APTR GFX_CreateImageInternal(int w, int h, int bpp, int extra, HDC * ret)
{
	BITMAPINFO bhdr;
	HBITMAP    bmp;
	DATA8      bits;
	HDC        hex = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC        hdc = CreateCompatibleDC(hex);

	DeleteDC(hex);

	#define	hdr   bhdr.bmiHeader
	memset(&hdr, 0, sizeof hdr);
	hdr.biSize = sizeof hdr;
	hdr.biWidth = w;
	hdr.biHeight = -h;
	hdr.biPlanes = 1;
	hdr.biBitCount = bpp;
	hdr.biCompression = BI_RGB;
	#undef hdr

	bmp = CreateDIBSection(hdc, &bhdr, DIB_RGB_COLORS, (LPVOID) &bits, NULL, 0);
	if (bmp == 0 || bits == NULL)
	{
		DeleteDC(hdc);
		return NULL;
	}
	if (ret) *ret = hdc;
	else DeleteDC(hdc);

	Image img = malloc(sizeof *img + extra);
	img->width   = w;
	img->height  = h;
	img->bpp     = bpp;
	img->format  = bpp == 8 ? GFX_FormatK : (bpp == 24 ? GFX_FormatBGR : GFX_FormatABGR);
	img->stride  = (w * (bpp/8) + 3) & ~3;
	img->encoder = 0;
	img->bitmap  = bits;
	img->hbitmap = bmp;
	img->palette = NULL;
	return img;
}

DLLIMP APTR GFX_GetSysImage(STRPTR path, STRPTR * desc, STRPTR * type)
{
	LPWSTR     str;
	SHFILEINFO info = {};
	HIMAGELIST list;
	IMAGEINFO  sys;
	Image      image;
	int        flags = SHGFI_SYSICONINDEX;
	int        attr  = FILE_ATTRIBUTE_NORMAL;

	if (path == NULL) return NULL;
	if (strncmp(path, "open:",  5) == 0) flags |= SHGFI_OPENICON,  path += 5;
	if (strncmp(path, "small:", 6) == 0) flags |= SHGFI_SMALLICON, path += 6;
	if (*path == '.') flags |= SHGFI_USEFILEATTRIBUTES;
	if (strcmp(path, ".folder") == 0) attr = FILE_ATTRIBUTE_DIRECTORY;

	UTF8ToUTF16(path, str);

	if (desc) flags |= SHGFI_DISPLAYNAME;
	if (type) flags |= SHGFI_TYPENAME;

	/* Do not free the image list returned by this function */
	list = (HIMAGELIST) SHGetFileInfo(str, attr, &info, sizeof info, flags);

	if (info.hIcon) DestroyIcon(info.hIcon);

	if (pImageList_GetImageInfo(list, info.iIcon, &sys))
	{
		HDC hdc;
		int extra = 0, sz, i;
		if (desc) extra += WideCharToMultiByte(CP_UTF8, 0, info.szDisplayName, -1, NULL, 0, 0, 0);
		if (type) extra += WideCharToMultiByte(CP_UTF8, 0, info.szTypeName,    -1, NULL, 0, 0, 0);

		image = GFX_CreateImageInternal(sys.rcImage.right - sys.rcImage.left, sys.rcImage.bottom - sys.rcImage.top, 32,
		            extra, &hdc);

		if (image)
		{
			HBITMAP old = SelectObject(hdc, image->hbitmap);
			DATA8   s, d, mask;

			/* Let's do it quick'n dirty: GDI API is such a PITA anyway */
			pImageList_Draw(list, info.iIcon, hdc, 0, 0, ILD_MASK);
			mask = alloca(sz = image->width * image->height);
			for (s = image->bitmap, d = mask, i = sz; i > 0; *d++ = (255-*s), s+=4, i --);
			pImageList_Draw(list, info.iIcon, hdc, 0, 0, ILD_TRANSPARENT);
			for (d = image->bitmap, s = mask; sz > 0; d+=4, s++, sz --) {
				/* Usual half-assed job: sometimes alpha is correct, sometimes it is not */
				if (d[3] > 0) continue; d[3] = *s;
				if (*s > 0) continue;
				d[0] = d[0] * d[3] / 255; /* Pre-multiply for AlphaBlend() */
				d[1] = d[1] * d[3] / 255;
				d[2] = d[2] * d[3] / 255;
			}
			image->encoder |= GFX_Premultiplied;
			SelectObject(hdc, old);
			DeleteDC(hdc);
			path = (STRPTR) (image+1);
			if (desc) path += WideCharToMultiByte(CP_UTF8, 0, info.szDisplayName, -1, *desc = path, extra, NULL, NULL);
			if (type) WideCharToMultiByte(CP_UTF8, 0, info.szTypeName, -1, *type = path, extra, NULL, NULL);
			return image;
		}
	}
	return NULL;
}

DLLIMP Bool GFX_ScreenCap(Image dest, int x, int y)
{
	if (dest->hbitmap == 0) return False;
	HDC hex = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC hdc = CreateCompatibleDC(hex);
	HBITMAP old = SelectObject(hdc, dest->hbitmap);

	BOOL ret = BitBlt(hdc, 0, 0, dest->width, dest->height, hex, x, y, SRCCOPY | CAPTUREBLT);
	SelectObject(hdc, old);

	DeleteDC(hdc);
	DeleteDC(hex);
	return ret;
}

DLLIMP APTR GFX_CreateImage(int w, int h, int bpp)
{
	return GFX_CreateImageInternal(w, h, bpp, 0, NULL);
}

DLLIMP APTR GFX_CreateImageEx(int w, int h, int bpp, int extra)
{
	return GFX_CreateImageInternal(w, h, bpp, extra, NULL);
}

/* Area being refreshed with OnPaint events */
DLLIMP void GFX_GetRefresh(GC gc, Rect * r)
{
	if (gc)
		memcpy(r, &gc->refresh, sizeof *r);
	else
		memset(r, 0, sizeof *r);
}

DLLIMP void GFX_ExcludeClipRect(GC gc, int x, int y, int w, int h)
{
	ExcludeClipRect(gc->device, x, y, x+w, y+h);
}

DLLIMP void GFX_GetPos(GC gc, int * x, int * y)
{
	POINT pt;

	GetCurrentPositionEx(gc->device, &pt);

	*x = pt.x;
	*y = pt.y;
}

DLLIMP long GFX_GetColor(GC gc, Bool bg)
{
	if (bg) return gc->bgcolor;
	else    return gc->fgcolor;
}

/* Use to handle custom draw */
DLLIMP int GFX_GetStage(GC gc)
{
	return gc->stage;
}

/* Remove replaced object while drawing */
DLLIMP void GFX_Free(GC gc)
{
	SIT_Widget w = gc->ctrl;

	if (w && w->sw_GC == gc) {
		gc->inuse --;
		if (gc->inuse > 0) return;
		w->sw_GC = NULL;
	}

	GFX_DELBGBRUSH(gc);
	if (gc->pen)  DeleteObject(SelectObject(gc->device, gc->pen));
	if (gc->font) SelectObject(gc->device, gc->font);
	switch (gc->free) {
	case 1:
		ReleaseDC(gc->hwnd, gc->device);
		free(gc);
		break;
	case 2:
		DeleteObject(SelectObject(gc->device, gc->bitmap));
		DeleteDC(gc->device);
		free(gc);
	}
}

DLLIMP void GFX_FreeImage(Image img)
{
	if (img)
	{
		if (img->hbitmap) DeleteObject(img->hbitmap);
		free(img);
	}
}

DLLIMP APTR GFX_LoadFont(STRPTR family, int h, int weight, int style)
{
	LPWSTR name;

	UTF8ToUTF16(family, name);

	return CreateFont(h, 0, 0, 0, weight,
		(style & GFX_FSItalic)    ? TRUE : FALSE,
		(style & GFX_FSUnderline) ? TRUE : FALSE,
		(style & GFX_FSStrike)    ? TRUE : FALSE,
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE,
		name
	);
}

void GFX_FillLogFont(STRPTR fmt, LPLOGFONT font)
{
	STRPTR name = strcpy(alloca(strlen(fmt) + 1), fmt);
	STRPTR p;
	int    h = 0, rel = 0, n;

	memset(font, 0, sizeof *font);
	font->lfCharSet = DEFAULT_CHARSET;
	font->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;

	for (p = strchr(name, '/'); p; p = strchr(p, '/'))
	{
		*p ++ = 0;
		if (strncasecmp(p, "underline", 9) == 0)
			font->lfUnderline = TRUE;
		else if (strncasecmp(p, "bold", 4) == 0)
			font->lfWeight = FW_BOLD;
		else if (strncasecmp(p, "italic", 6) == 0)
			font->lfItalic = TRUE;
		else if (strncasecmp(p, "strike", 6) == 0)
			font->lfStrikeOut = TRUE;
		else if (strncasecmp(p, "noaa", 4) == 0)
			font->lfQuality = NONANTIALIASED_QUALITY;
		else if ((h = strtol(p, &p, 10)) > 0)
			rel = *p == '%';
	}

	n = FindInList("system,monospace,fixed", name, 0);

	if (n >= 0 || h == 0 || rel)
	{
		LOGFONT alt;

		if (sitfont)
			memcpy(&alt, sitfont, sizeof alt);
		else
			GetObject(GetStockObject(n > 0 ? SYSTEM_FIXED_FONT : DEFAULT_GUI_FONT), sizeof alt, &alt);

		if (n >= 0) wcscpy(font->lfFaceName, alt.lfFaceName);
		if (rel)    h = alt.lfHeight * h / 100;
		if (h == 0) h = alt.lfHeight;
	}
	if (font->lfFaceName[0] == 0)
		MultiByteToWideChar(CP_UTF8, 0, name, -1, font->lfFaceName, LF_FACESIZE);

	font->lfHeight = h;
}

void GFX_SetSystemFont(LPWSTR wfmt)
{
	int    max = UTF16ToUTF8(NULL, 0, (STRPTR) wfmt, -1);
	STRPTR fmt = alloca(max);
	UTF16ToUTF8(fmt, max, (STRPTR) wfmt, -1);
	LPLOGFONT lpfont = malloc(sizeof *sitfont);
	GFX_FillLogFont(fmt, lpfont);
	sitfont = lpfont;
	sithfont = CreateFontIndirect(sitfont);
}

DLLIMP APTR GFX_GetDevice(GC gc)
{
	return gc->device;
}

DLLIMP APTR GFX_GetFont(STRPTR fmt)
{
	LOGFONT font;
	/* Most common case */
	if (strcasecmp(fmt, "system") == 0)
		return sithfont ? sithfont : GetStockObject(DEFAULT_GUI_FONT);
	GFX_FillLogFont(fmt, &font);
	return CreateFontIndirect(&font);
}

DLLIMP void GFX_CloseFont(APTR hfont)
{
	DeleteObject(hfont);
}

DLLIMP void GFX_SetFont(GC gc, APTR font)
{
	HFONT old = SelectObject(gc->device, font);

	if (gc->font == 0)
		gc->font = old;
}

DLLIMP void GFX_GetFontInfo(GC gc, TextFont * out)
{
	TEXTMETRIC tm;
	WCHAR buffer[40];

	GetTextMetrics(gc->device, &tm);

	out->height   = tm.tmHeight;
	out->ascent   = tm.tmAscent;
	out->descent  = tm.tmDescent;
	out->avgwidth = tm.tmAveCharWidth;
	out->maxwidth = tm.tmMaxCharWidth;
	out->fixed    = ! (tm.tmPitchAndFamily & TMPF_FIXED_PITCH);

	/* Get font family name */
	GetTextFace(gc->device, DIM(buffer), buffer);
	UTF16ToUTF8(out->name, sizeof out->name, (STRPTR) buffer, -1);
}

